package org.example.handwriting.spring;

import org.example.handwriting.spring.Annotation.Component;
import org.example.handwriting.spring.interfaces.BeanPostProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;

public class ApplicationContext {

    private static final Logger logger = LoggerFactory.getLogger(ApplicationContext.class);

    private HashMap<String, Object> ioc = new HashMap<>();

    /**
     * 未加载完的bean 用于解决依赖循环
     */
    private HashMap<String, Object> loadingIoc = new HashMap<>();
    private HashMap<String, BeanDefinition> beanDefinitionHashMap = new HashMap<>();

    private ArrayList<BeanPostProcessor> beanPostProcessorArrayList = new ArrayList<>();

    public ApplicationContext(String packageName) throws IOException, URISyntaxException {
        initContext(packageName);
    }

    /**
     * 初始化容器
     */
    private void initContext(String packageName) throws IOException, URISyntaxException {
        ArrayList<Class<?>> arrayList = scanPackage(packageName);

        // 先把BeanDefinition加载
        for (Class<?> x : arrayList) {
            if (scanCreate(x)) {
                wrapper(x);
//                createBean(wrapper);
            }
        }

        initializeBeanPostProcessor();

         // 再初始化BeanDefinition
        for (BeanDefinition value : beanDefinitionHashMap.values()) {
            createBean(value);
        }
    }

    private void initializeBeanPostProcessor() {

        for (BeanDefinition value : beanDefinitionHashMap.values()) {
            if(BeanPostProcessor.class.isAssignableFrom(value.getBeanType())){
                Object bean = createBean(value);
                BeanPostProcessor change = BeanPostProcessor.class.cast(bean);
                beanPostProcessorArrayList.add(change);
            }
        }
    }

    /**
     * 判断当前类是否拥有Component
     * 相当于这个类是否需要加载放到容器中
     */
    protected boolean scanCreate(Class<?> type) {
        return type.isAnnotationPresent(Component.class);
    }

    /**
     * 转为BeanDefinition
     */
    protected BeanDefinition wrapper(Class<?> type) {
        BeanDefinition beanDefinition = new BeanDefinition(type);

        if (beanDefinitionHashMap.containsKey(beanDefinition.getName())) {
            throw new RuntimeException("bean名字重复");
        }

        beanDefinitionHashMap.put(beanDefinition.getName(), beanDefinition);
        return new BeanDefinition(type);
    }

    /**
     * 创建bean
     */
    protected Object createBean(BeanDefinition beanDefinition) {
        String name = beanDefinition.getName();
        if (ioc.containsKey(name)) {
            return ioc.get(name);
        }

        if(loadingIoc.containsKey(name)){
            return loadingIoc.get(name);
        }

        // 发现ioc容器没有这个bean 就去创建
        return doCreateBean(beanDefinition);
    }

    private Object doCreateBean(BeanDefinition beanDefinition) {
        Constructor<?> constructor = beanDefinition.getConstructor();
        Object bean = null;
        try {
            bean = constructor.newInstance();
            loadingIoc.put(beanDefinition.getName(),bean);

            autowiredBean(bean, beanDefinition.getFieldArrayList());
            bean = initializeBean(beanDefinition, bean);

            ioc.put(beanDefinition.getName(), bean);
            loadingIoc.remove(beanDefinition.getName());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return bean;
    }

    private Object initializeBean(BeanDefinition beanDefinition, Object bean) {
        for(BeanPostProcessor beanPostProcessor : beanPostProcessorArrayList){
            bean = beanPostProcessor.beforeInitializeBean(bean,beanDefinition.getName());
        }

        invokeMethod(bean, beanDefinition.getMethodArrayList());

        for(BeanPostProcessor beanPostProcessor : beanPostProcessorArrayList){
            bean = beanPostProcessor.afterInitializeBean(bean,beanDefinition.getName());
        }

        return bean;
    }

    /**
     * 调用PostConstruct注解的方法
     */
    private void invokeMethod(Object bean, ArrayList<Method> methods) {
        for (Method method : methods) {
            try {
                method.invoke(bean);
            } catch (IllegalAccessException | InvocationTargetException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 自动注入
     */
    private void autowiredBean(Object bean, ArrayList<Field> fields) {
        for (Field field : fields) {
            field.setAccessible(true); // 可访问
            try {
                field.set(bean,getBean(field.getType()));
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 扫描包
     */
    private ArrayList<Class<?>> scanPackage(String packageName) throws IOException, URISyntaxException {
        ArrayList<Class<?>> res = new ArrayList<>();

        // a.b.c
        URL resource = this.getClass().getClassLoader().getResource(packageName.replace(".", File.separator));

        // window系统的解析路径的方式 也可以用这个Path path = Paths.get(resource.toURI());
        File file = new File(resource.toURI());
        Path path = Path.of(file.toURI());


        // 递归遍历文件夹
        Files.walkFileTree(path, new SimpleFileVisitor<>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { // 每一次到文件 如何处理
                Path absolutePath = file.toAbsolutePath(); // 拿到绝对路径
                String absolutePathString = absolutePath.toString();
                if (absolutePathString.endsWith(".class")) { // 如果这个文件是class的话

                    // 拿到包下的全类名
                    String replace = absolutePathString.replace(File.separator, ".");
                    int index = replace.indexOf(packageName);
                    String substring = replace.substring(index, replace.length() - ".class".length());
                    try {
                        res.add(Class.forName(substring));
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }
                return FileVisitResult.CONTINUE;
            }
        });
        logger.info(res.toString());
        return res;
    }

    /**
     * 根据name 拿到类
     */
    public Object getBean(String name) {
        if(name == null){
            return null;
        }

        // 先看看ioc有没有这个bean
        Object bean = ioc.get(name);
        if (bean != null) {
            return bean;
        }


        // 再看看beanDefinitionHashMap有没有 没有就创建
        if(beanDefinitionHashMap.containsKey(name)){
            return createBean(beanDefinitionHashMap.get(name));
        }

        return null;
    }

    /**
     * 根据类型拿到类
     */
    public <T> T getBean(Class<T> beanType) {
        String beanName = null;
        Collection<BeanDefinition> definitions = beanDefinitionHashMap.values();
        for (BeanDefinition beanDefinition : definitions) {
            if (beanType.isAssignableFrom(beanDefinition.getBeanType())) {
                beanName = beanDefinition.getName();

            }
        }

        return (T) getBean(beanName);
    }

    /**
     * 根据类型拿到所有类
     */
    public <T> ArrayList<T> getBeans(Class<T> beanType) {
        ArrayList<T> res = new ArrayList<>();
        Collection<BeanDefinition> definitions = beanDefinitionHashMap.values();
        for (BeanDefinition beanDefinition : definitions) {
            if (beanType.isAssignableFrom(beanDefinition.getBeanType())) {
                String beanName = beanDefinition.getName();
                Object bean = getBean(beanName);
                res.add((T)bean);
            }
        }

        return res;
    }
}
